iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
自我挑戰組

模仿知名網站的外觀系列 第 12

【Day12】模仿知名網站的外觀 Instagram(12) Story區塊

  • 分享至 

  • xImage
  •  

我們上回完成了CreateModal,接下來要完成的是Story,構成Story有兩個主要的部分:最上面的進度條、下面隨著進度條變換的圖片,以下內容會圍繞在這兩個地方,其餘不重要的部分就會省略。

在Components資料夾底下,新增StoryComponents資料夾。

在StoryComponents資料夾底下,新增Progressbar.jsx、Progressbar.css、StoryViewer.jsx。

首先編寫Progressbar.jsx,設定進度條的進度會隨著時間而變化。

import React, { useEffect, useState } from "react";
import "./Progressbar.css";

const Progressbar = ({index, activeIndex, duration}) => {
	const [progress, setProgress] = useState(0);

	useEffect(() => {
		const interval = setInterval(() => {
			setProgress((preProgress) => {
				if (preProgress < 100) {
					return preProgress + 1;
				}
				clearInterval(interval);
				return preProgress;
			});
		}, duration / 100);

		return () => {
			clearInterval(interval);
		};
	}, [duration, activeIndex]);

	useEffect(() => {
		setProgress(0);
	}, [activeIndex]);

	const isActive = index === activeIndex;
	return (
		<div>
			<div
				className={`${isActive ? "progress-bar" : ""}`}
				style={{ width: `${progress}%` }}
			></div>
		</div>
	);
};

export default Progressbar;

在編寫StoryViewer.jsx前,先安裝會等下使用到的函式庫。

npm i styled-components

安裝完成後,開始將以下的內容寫入StoryViewer.jsx,圖片會不斷的每兩千毫秒變換,如果顯示到最後一張圖片,就回到第一張圖開始重複顯示。

import React, { useEffect, useState } from "react";
import { styled } from "styled-components";

const StoryViewerContainer = styled.div`
	display: flex;
	justify-content: center;
	align-items: center;
	height: 100vh;
	background-color: black;
`;

const StoryImage = styled.img`
	max-height: 90vh;
	object-fit: contain;
`;

const StoryViewer = ({ stories }) => {
	const [currentStoryIndex, setCurrentStoryIndex] = useState(0);
	const [activeIndex, setActiveIndex] = useState(0);

	const handleNextStory = () => {
		if (currentStoryIndex < stories.length - 1) {
			setCurrentStoryIndex(currentStoryIndex + 1);
			setActiveIndex(activeIndex + 1);
		} else if (currentStoryIndex === stories.length - 1) {
			setCurrentStoryIndex(0);
			setActiveIndex(0);
		}
	};

	useEffect(() => {
		const interval = setInterval(() => {
			handleNextStory();
		}, 2000);
		return () => clearInterval(interval);
	}, [currentStoryIndex]);

	return (
		<div>
			<StoryViewerContainer>
				<StoryImage src={stories?.[currentStoryIndex].image} />
			</StoryViewerContainer>
		</div>
	);
};

export default StoryViewer;

在Pages資料夾底下,新增Story資料夾。

在Story資料夾底下,新增Story.jsx。

在Story.jsx中,先顯示我們的StoryViewer,至於Progressbar.jsx的內容以後處理。

在這裏將要顯示的圖片存放於stories中。

import React from "react";
import StoryViewer from "../../Components/StoryComponents/StoryViewer";

const Story = () => {
	const stories = [
		{
			image:
				"https://images.pexels.com/photos/15978352/pexels-photo-15978352.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
		},
		{
			image:
				"https://images.pexels.com/photos/14969818/pexels-photo-14969818.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
		},
		{
			image:
				"https://images.pexels.com/photos/15327222/pexels-photo-15327222.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
		},
		{
			image:
				"https://images.pexels.com/photos/18074917/pexels-photo-18074917.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
		},
		{
			image:
				"https://images.pexels.com/photos/8451450/pexels-photo-8451450.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
		},
	];
	return (
		<div>
			<StoryViewer stories={stories} />
		</div>
	);
};

export default Story;

然後來到Router.jsx,添加story的路由。

import React from 'react'
import Sidebar from "../../Components/Sidebar/Sidebar"
import { Route, Routes } from "react-router-dom"
import HomePage from "../HomePage/HomePage"
import Profile from "../Profile/Profile"
import Story from "../Story/Story"

const Router = () => {
  return (
    <div>
      <div className="flex">
        <div className="w-[20%] border">
          <Sidebar />
        </div>
        <div className="w-full">
          <Routes>
            <Route path="/" element={<HomePage />} />
            <Route path="/username" element={<Profile />} />
            <Route path="/story" element={<Story />} />
          </Routes>
        </div>
      </div>
    </div>
  )
}

export default Router

啟動專案後,到http://localhost:5173/story,就能看到不斷輪流顯示的圖片。

Untitled

接下來,讓進度條顯示在畫面上,修改StoryViewer.jsx,傳入一些參數給Progressbar。

import React, { useEffect, useState } from "react";
import { styled } from "styled-components";
import Progressbar from "./Progressbar";

const StoryViewerContainer = styled.div`
	display: flex;
	justify-content: center;
	align-items: center;
	height: 100vh;
	background-color: black;
`;

const StoryImage = styled.img`
	max-height: 90vh;
	object-fit: contain;
`;

const StoryViewer = ({ stories }) => {
	const [currentStoryIndex, setCurrentStoryIndex] = useState(0);
	const [activeIndex, setActiveIndex] = useState(0);

	const handleNextStory = () => {
		if (currentStoryIndex < stories.length - 1) {
			setCurrentStoryIndex(currentStoryIndex + 1);
			setActiveIndex(activeIndex + 1);
		} else if (currentStoryIndex === stories.length - 1) {
			setCurrentStoryIndex(0);
			setActiveIndex(0);
		}
	};

	useEffect(() => {
		const interval = setInterval(() => {
			handleNextStory();
		}, 2000);
		return () => clearInterval(interval);
	}, [currentStoryIndex]);

	return (
		<div className="relative w-full">
			<StoryViewerContainer>
				<StoryImage src={stories?.[currentStoryIndex].image} />
				<div className="absolute top-0 flex w-full">
					{stories.map((item, index) => (
						<Progressbar
							key={index}
							duration={2000}
							index={index}
							activeIndex={activeIndex}
						/>
					))}
				</div>
			</StoryViewerContainer>
		</div>
	);
};

export default StoryViewer;

到Progressbar.css,設定進度條的CSS。

.progress-bar-container{
    width: 100%;
    height: 4px;
    background-color: gray;
    opacity: .65;
    border-radius: 4px;
    overflow: hidden;
    transition: opacity .3 ease-out;
    margin: 1rem;
}

.progress-bar-container .active{
    opacity: 1;
}

.progress-bar{
    height: 100%;
    background-color: white;
    border-radius: 4px;
}

最後對Progressbar.jsx做一些小修改,導入CSS。

import React, { useEffect, useState } from "react";
import "./Progressbar.css";

const Progressbar = ({index, activeIndex, duration}) => {
	const [progress, setProgress] = useState(0);

	useEffect(() => {
		const interval = setInterval(() => {
			setProgress((preProgress) => {
				if (preProgress < 100) {
					return preProgress + 1;
				}
				clearInterval(interval);
				return preProgress;
			});
		}, duration / 100);

		return () => {
			clearInterval(interval);
		};
	}, [duration, activeIndex]);

	useEffect(() => {
		setProgress(0);
	}, [activeIndex]);

	const isActive = index === activeIndex;

	return (
		<div className={`progress-bar-container ${isActive ? "active" : ""}`}>
            
			<div
				className={`${isActive ? "progress-bar" : ""}`}
				style={{ width: `${progress}%` }}
			></div>
		</div>
	);
};

export default Progressbar;

進度條完成後,我們的Story區塊就全部完成了。

Untitled


上一篇
【Day11】模仿知名網站的外觀 Instagram(11) 建立新貼文-2
下一篇
【Day13】模仿知名網站的外觀 Instagram(13) 搜尋區塊
系列文
模仿知名網站的外觀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言